package org.rugby.online.persistency;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.rugby.online.core.exception.RboException;
import org.rugby.online.core.exception.RboExceptionType;

public class DefaultPersistencyStrategy implements RboPersistencyStragtegy {

	/**
	 * Singleton instance
	 */
	private static DefaultPersistencyStrategy strategy;

	/**
	 * Main path for persistence
	 */
	private static final String MAIN_PATH = "./rso";

	/**
	 * Map of path and classes
	 */
	private static Map<Class<? extends Serializable>, String> PATH_MAP = new ConcurrentHashMap<Class<? extends Serializable>, String>(
			0);

	/**
	 * Private constructor
	 */
	private DefaultPersistencyStrategy() {
		super();
		initPathMap();
	}

	/**
	 * Initialize path map from default enumeration mapper
	 */
	private void initPathMap() {
		for (DefaultPersistencyMapper mapper : DefaultPersistencyMapper
				.values()) {
			PATH_MAP.put(mapper.getMappedClass(), mapper.getPath());
		}
	}

	public static RboPersistencyStragtegy getInstance() {
		if (strategy == null) {
			strategy = new DefaultPersistencyStrategy();
		}
		return strategy;
	}

	@Override
	public String getMainPersistencyPath() {
		return MAIN_PATH;
	}

	@Override
	public String getPersistencyPathForClass(Class<? extends Serializable> clazz) {
		String path = this.getMainPersistencyPath();
		if (PATH_MAP.containsKey(clazz)) {
			path += "/" + PATH_MAP.get(clazz);
		}
		return path;
	}

	@Override
	public List<Class<? extends Serializable>> getPersistencyClassList() {
		List<Class<? extends Serializable>> list = new ArrayList<Class<? extends Serializable>>(
				PATH_MAP.keySet());
		return list;
	}

	@Override
	public boolean writeObject(Class<? extends Serializable> clazz, long id,
			Serializable obj) throws RboException {

		// Making the directory if not exists
		FileUtils.makeDirIntoPath(MAIN_PATH, PATH_MAP.get(clazz));

		// Getting the file name
		String filename = getFileName(clazz, id);

		// Create the file : delete it if already exists
		FileUtils.createNewFileIntoPath(this.getPersistencyPathForClass(clazz),
				filename);
		
		// Get the path to the file
		String filePath = getPersistencyPathForClass(clazz) + "/"
				+ getFileName(clazz, id);

		try {
			// Writing the object with serialization
			ObjectOutputStream oos = new ObjectOutputStream(
					new FileOutputStream(filePath));
			oos.writeObject(obj);
			oos.flush();
			oos.close();
		} catch (FileNotFoundException e) {
			throw new RboException(RboExceptionType.ERROR, "file " + filename
					+ " not found :\n", e);
		} catch (IOException e) {
			throw new RboException(RboExceptionType.ERROR, "I/O for file "
					+ filename + " :\n", e);
		}
		return true;
	}

	@Override
	public Serializable getObject(Class<? extends Serializable> clazz, long id)
			throws RboException {

		// Getting path to the file
		String filePath = getPersistencyPathForClass(clazz) + "/"
				+ getFileName(clazz, id);
		File fileObject = new File(filePath);

		Serializable obj = null;

		try {
			// Reading object
			FileInputStream fis = new FileInputStream(fileObject);
			ObjectInputStream ois = new ObjectInputStream(fis);
			obj = (Serializable) ois.readObject();
			ois.close();
		} catch (FileNotFoundException e) {
			throw new RboException(RboExceptionType.ERROR, "file object "
					+ filePath + " does not exist.", e);
		} catch (IOException e) {
			throw new RboException(RboExceptionType.ERROR, "I/O for file "
					+ filePath + " :\n", e);
		} catch (ClassNotFoundException e) {
			throw new RboException(RboExceptionType.ERROR,
					"Class not found from file " + filePath + " :\n", e);
		}
		return obj;
	}

	@Override
	public boolean updateObject(Class<? extends Serializable> clazz, long id,
			Serializable obj) throws RboException {

		// Getting path to the file
		String filePath = getPersistencyPathForClass(clazz) + "/"
				+ getFileName(clazz, id);
		File fileObject = new File(filePath);

		if (!fileObject.exists()) {
			throw new RboException(RboExceptionType.ERROR, "file object "
					+ filePath + " for UPDATE does not exist : nothing done.");
		} else {
			// Re-write the object directly
			this.writeObject(clazz, id, obj);
		}

		return true;
	}

	@Override
	public boolean deleteObject(Class<? extends Serializable> clazz, long id)
			throws RboException {
		// Getting path to the file
		String filePath = getPersistencyPathForClass(clazz) + "/"
				+ getFileName(clazz, id);
		File fileObject = new File(filePath);
		
		// Delete the object
		if (!fileObject.delete()) {
			throw new RboException(RboExceptionType.WARNING, "file object "
					+ filePath + " for DELETE does not exist.");
		}
		return true;
	}

	@Override
	public void init() throws RboException {
		if (strategy == null) {
			strategy = new DefaultPersistencyStrategy();
		}
		if (strategy == null) {
			throw new RboException(RboExceptionType.ERROR,
					"Persistency strategy failed to init.");
		}
	}

	/**
	 * Get the file name in this strategy
	 * 
	 * @param clazz
	 *            the class name
	 * @param id
	 *            the object identifier
	 * @return the file name
	 */
	private String getFileName(Class<?> clazz, long id) {
		return clazz.getSimpleName() + "_" + String.valueOf(id) + ".rso";
	}

}
